package pong.game.objects;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

import pong.mathematic.Vector2D;
import pong.mathematic.Vector3D;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

public abstract class GL_Object {

	private static final int EMPTY = -1;

	private static final String TAG = "GL_Object";

	private static int whiteTextureID = 0;

	private static float[] vertexes = new float[12];
	private static FloatBuffer triangleBuffer = null;
	private static FloatBuffer textureBuffer = null;

	public float height = 0;
	public float width = 0;

	protected int[] textures = { 0 };

	protected boolean needLoadNewTexture = false;
	protected Bitmap newTexture = null;
	protected Bitmap imageBitmap = null;
	protected Vector3D center = new Vector3D();

	protected float scaleValue = 1f;
	protected float rotationAngle = 0f;

	private float curAlpha = 1f;

	protected boolean isAlphaAnimating = false;
	protected boolean isScaleAnimating = false;
	protected boolean isRotateAnimating = false;
	protected boolean isWhite = false;

	private long startAlphaTime = EMPTY;
	private long startScaleTime = EMPTY;
	private long startRotateTime = EMPTY;

	private long animationAlphaTime = EMPTY;
	private long animationScaleTime = EMPTY;
	private long animationRotateTime = EMPTY;
	private float startAlphaValue;
	private float finishAlphaValue;

	private float startRotateValue;
	private float finishRotateValue;

	private float startScaleValue;
	private float finishScaleValue;

	private final Object textureLocker = new Object();

	public GL_Object() {
		// this(new Vector3D(0, 0, 0), 0.5f, 0.5f, 0, 1);
		this(new Vector3D(0, 0, 0), 0.5f, 0.5f);
	}

	public GL_Object(Vector3D center, float width, float height) {// , int
																	// visibleTime,
																	// float
																	// maxCurAlpha)
																	// {
		this.width = width;
		this.height = height;
		this.center = center;
		initVertexesForNewSize(width, height);
		this.startAlphaTime = System.currentTimeMillis();
		this.startScaleTime = this.startAlphaTime;
		// this.visibleTime = startTime + visibleTime;
		// this.beginShow(maxCurAlpha, visibleTime);
	}

	private void initTextures() {

		if (textureBuffer != null)
			return;

		float[] texture = { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f };

		ByteBuffer bb = ByteBuffer.allocateDirect(texture.length * 4);
		bb.order(ByteOrder.nativeOrder());
		textureBuffer = bb.asFloatBuffer();
		textureBuffer.put(texture);
		textureBuffer.position(0);
	}

	private void initVertexes() {

		if (triangleBuffer != null)
			return;

		vertexes[0] = -0.5f;
		vertexes[1] = 0.5f;
		vertexes[2] = center.getZ();

		vertexes[3] = -0.5f;
		vertexes[4] = -0.5f;
		vertexes[5] = center.getZ();

		vertexes[6] = 0.5f;
		vertexes[7] = 0.5f;
		vertexes[8] = center.getZ();

		vertexes[9] = 0.5f;
		vertexes[10] = -0.5f;
		vertexes[11] = center.getZ();

		ByteBuffer bb = ByteBuffer.allocateDirect(vertexes.length * 4);
		bb.order(ByteOrder.nativeOrder());
		triangleBuffer = bb.asFloatBuffer();
		triangleBuffer.put(vertexes);
		triangleBuffer.position(0);
	}

	protected void initVertexesForNewSize(float width, float height) {
		this.height = height;
		this.width = width;
		initTextures();
		initVertexes();
	}

	public void render(GL10 gl, boolean fastTexture) {
		if (needLoadNewTexture && newTexture != null) {
			synchronized (textureLocker) {
				if (!newTexture.isRecycled()) {
					Log.v(TAG, "LOAD TEXTURE TO VIDEO RAM");
					// delete old texture from VIDEO RAM
					if (hasTexture()) {
						gl.glDeleteTextures(1, textures, 0);
						Log.d(TAG, "Texture Loading. Old texture remove from video RAM "
								+ this.getClass().getSimpleName());
					}
					this.setTexture(GL_Helper.loadGLTexture(gl, newTexture, fastTexture));
					if (textures != null && hasTexture()) {
						// must be called!!!
						onTextureLoadedToGL(newTexture);
					}
					newTexture = null;
				} else {
					// TODO: There is PROBLEM!!!
					Log.e(TAG, "!!!!!!!!!! Cannot load recycled bitmap !!!!!!!! - " + this.getClass().getSimpleName());
				}
				needLoadNewTexture = false;
			}
		}

		if (textures != null && hasTexture()) {
			draw(gl);
		}
	}

	/**
	 * @return true if item has an texture in video RAM
	 */
	public boolean hasTexture() {
		return textures[0] != 0;
	}

	/**
	 * Method will be called then new texture has been loaded to GL
	 * 
	 * @param bitmap
	 */
	protected void onTextureLoadedToGL(Bitmap bitmap) {
		GL_Helper.recycleBitmap(bitmap);
		clearBitmapImage();
		if (!GL_Helper.isStaticBitmap(bitmap)) {
			Log.d(TAG, "Textures count in memory " + --GL_Helper.textureCount);
			// if (GL_Helper.getInstance().bitmapQueueIsLoadFinished()) {
			// // if (CanvasData.textureCount == 0) {
			// GL_Helper.getInstance().removeTempBackground(0);
			// }
		}
	}

	public abstract boolean isIn(Vector2D point);

	public boolean hasBitmapImage() {
		return imageBitmap != null && !imageBitmap.isRecycled();
	}

	public void clearBitmapImage() {
		GL_Helper.recycleBitmap(imageBitmap);
		imageBitmap = null;
	}

	/*
	 * public void loadBitmapImageToGL() { if (hasBitmapImage()) {
	 * setTexture(imageBitmap); } }
	 */

	protected void updateAlphaFromTime() {
		int curDuration = (int) (System.currentTimeMillis() - startAlphaTime);
		if (curDuration < 0) {
			curDuration = 0;
		}
		float alpha;
		if (curDuration >= animationAlphaTime) {
			alpha = finishAlphaValue;
			isAlphaAnimating = false;
		} else {
			alpha = startAlphaValue + (finishAlphaValue - startAlphaValue) * curDuration / animationAlphaTime;
		}
		this.setAlpha(alpha);
	}

	public void startAlphaAnimation(float startAlpha, float finishAlpha, int duration) {
		startAlphaAnimationDelayed(startAlpha, finishAlpha, duration, 0);
	}

	public void startAlphaAnimationDelayed(float startAlpha, float finishAlpha, int duration, int delay) {
		isAlphaAnimating = true;
		startAlphaTime = System.currentTimeMillis() + delay;
		startAlphaValue = startAlpha;
		finishAlphaValue = finishAlpha;
		animationAlphaTime = duration;
	}

	public void startRotateAnimation(float startRotate, float finishRotate, int duration) {
		startRotateAnimationDelayed(startRotate, finishRotate, duration, 0);
	}

	public void startRotateAnimationDelayed(float startRotate, float finishRotate, int duration, int delay) {
		isRotateAnimating = true;
		startRotateTime = System.currentTimeMillis() + delay;
		startRotateValue = startRotate;
		finishRotateValue = finishRotate;
		animationRotateTime = duration;
	}

	// ////////////////////////////

	protected void updateScaleFromTime() {
		int curDuration = (int) (System.currentTimeMillis() - startScaleTime);
		if (curDuration < 0) {
			curDuration = 0;
		}
		float scale;
		if (curDuration >= animationScaleTime) {
			scale = finishScaleValue;
			isScaleAnimating = false;
		} else {
			scale = startScaleValue + (finishScaleValue - startScaleValue) * curDuration / animationScaleTime;
		}
		this.setScaleValue(scale);
	}

	protected void updateRotateFromTime() {
		int curDuration = (int) (System.currentTimeMillis() - startRotateTime);
		if (curDuration < 0) {
			curDuration = 0;
		}
		float rotate;
		if (curDuration >= animationRotateTime) {
			rotate = finishRotateValue;
			isRotateAnimating = false;
		} else {
			rotate = startRotateValue + (finishRotateValue - startRotateValue) * curDuration / animationRotateTime;
		}
		this.setRotateionAngle(rotate);
	}

	public void startScaleAnimation(float startScale, float finishScale, int duration) {
		startScaleAnimationDelayed(startScale, finishScale, duration, 0);
	}

	public void startScaleAnimationDelayed(float startScale, float finishScale, int duration, int delay) {
		isScaleAnimating = true;
		startScaleTime = System.currentTimeMillis() + delay;
		startScaleValue = startScale;
		finishScaleValue = finishScale;
		animationScaleTime = duration;
	}

	// ////////////////////////////

	private void draw(GL10 gl) {
		gl.glPushMatrix();

		if (isAlphaAnimating) {
			updateAlphaFromTime();
		}
		if (isScaleAnimating) {
			updateScaleFromTime();
		}
		if (isRotateAnimating)
			updateRotateFromTime();

		gl.glColor4f(1f, 1f, 1f, getAlpha());

		gl.glTranslatef(center.getX(), center.getY(), center.getZ());
		// if (this instanceof GL_ContentItem) {
		// Vector3D temp = new Vector3D(((GL_ContentItem)
		// this).getPhysicalCenterOffset());
		// gl.glTranslatef(-temp.getX(), -temp.getY(), -temp.getZ());
		// }
		gl.glRotatef(getRotationAngle(), 0, 0, 1);
		float scaleValue = getScaleValue();
		gl.glScalef(scaleValue * width, scaleValue * height, scaleValue);

		if (isWhite() && whiteTextureID != 0) {
			gl.glBindTexture(GL10.GL_TEXTURE_2D, whiteTextureID);
		} else {
			gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
		}

		// Point to our buffers
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

		gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, triangleBuffer);

		gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
		// Disable the client state before leaving
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

		// gl.glColor4f(1f, 1f, 1f, 1f);

		gl.glPopMatrix();
	}

	public boolean isNewTexturePrepared() {
		return needLoadNewTexture;
	}

	public void setCenter(Vector3D new_center) {
		setCenter(new_center.getX(), new_center.getY(), new_center.getZ());
	}

	public void setCenter(Vector2D new_center, float z) {
		setCenter(new_center.getX(), new_center.getY(), z);
	}

	public void setCenter(float x, float y, float z) {
		center.setXYZ(x, y, z);
	}

	public void setTexture(Bitmap texture) {
		synchronized (textureLocker) {

			if (texture != null && texture.isRecycled()) {
				Log.e(TAG, "Wrong bitmap! It is recicled! - " + this.getClass().getSimpleName());
			}
			if (newTexture != null) { // texture == newTexture
				if (!GL_Helper.isStaticBitmap(newTexture)) {
					Log.w(TAG, "Wrong case!!! Double assigne of the new Textxture! - "
							+ this.getClass().getSimpleName());
					onTextureLoadedToGL(newTexture);
				}

			}

			needLoadNewTexture = true;
			// check if texture size is ^2?
			int w = texture.getWidth();
			if ((w & (w - 1)) != 0) {
				newTexture = GL_Helper.cropBitmap(texture);
			} else {
				newTexture = texture;
			}
			if (!GL_Helper.isStaticBitmap(newTexture)) {
				Log.d(TAG, "Textures count in memory " + ++GL_Helper.textureCount);
			}

		}
	}

	/*
	 * public void setTexture(Drawable bitmap) { BitmapDrawable temp =
	 * (BitmapDrawable) bitmap; setTexture(temp.getBitmap()); }
	 */

	public void setTexture(int id) {
		textures[0] = id;
	}

	public void setTexture(int id, Context context) {
		synchronized (textureLocker) {

			Bitmap texture = BitmapFactory.decodeResource(context.getResources(), id);
			if (texture != null && texture.isRecycled()) {
				Log.e(TAG, "Wrong bitmap! It is recicled! - " + this.getClass().getSimpleName());
			}
			if (newTexture != null) { // texture == newTexture
				if (!GL_Helper.isStaticBitmap(newTexture)) {
					Log.w(TAG, "Wrong case!!! Double assigne of the new Textxture! - "
							+ this.getClass().getSimpleName());
					onTextureLoadedToGL(newTexture);
				}

			}

			needLoadNewTexture = true;
			// check if texture size is ^2?
			int w = texture.getWidth();
			if ((w & (w - 1)) != 0) {
				newTexture = GL_Helper.cropBitmap(texture);
			} else {
				newTexture = texture;
			}
			if (!GL_Helper.isStaticBitmap(newTexture)) {
				Log.d(TAG, "Textures count in memory " + ++GL_Helper.textureCount);
			}

		}
	}

	public void resetTextureID() {
		textures[0] = 0;
	}

	public int getTextureID() {
		return textures[0];
	}

	public void setRotationAngle(float rotationAngle) {
		this.rotationAngle = rotationAngle;
	}

	public void setScaleValue(float scaleValue) {
		this.scaleValue = scaleValue;
	}

	public void setRotateionAngle(float rotateValue) {
		this.rotationAngle = rotateValue;
	}

	public float getRotationAngle() {
		return rotationAngle;
	}

	public float getScaleValue() {
		return scaleValue;
	}

	protected void setImageBitmap(Bitmap bitmap) {
		GL_Helper.recycleBitmap(imageBitmap);
		// this.imageBitmap = bitmap;
	}

	public Bitmap getImageBitmap() {
		return this.imageBitmap;
	}

	public Vector3D getCenter() {
		return center;
	}

	public float getAlpha() {
		return curAlpha;
	}

	public long getStartAlphaTime() {
		return startAlphaTime;
	}

	public void setAlpha(float curAlpha) {
		this.curAlpha = curAlpha;
		if (curAlpha < 0) {
			Log.e(TAG, "The Alpha Value must be bigger than 0");
		}
		// if (curAlpha > maxAlpha) {
		// this.curAlpha = maxAlpha;
		// }
	}

	public boolean isScaleAnimation() {
		return this.isScaleAnimating;
	}

	public boolean isAlphaAnimation() {
		return this.isAlphaAnimating;
	}

	public boolean isRotateAnimating() {
		return this.isRotateAnimating;
	}

	public boolean isWhite() {
		return isWhite;
	}

	public void setWhite(boolean value) {
		this.isWhite = value;
	}

	public static void setWhiteTextureID(int id) {
		whiteTextureID = id;
	}
	// public void setVisibleBarier(int visibleBarier) {
	// this.visibleBarier = visibleBarier;
	// }
}
